package pl.com.qapps.unity;

import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import pl.com.qapps.unity.UnityPb.NotificationPb;
import pl.com.qapps.unity.UnityPb.RequestPb;
import pl.com.qapps.unity.UnityPb.RequestPb.Type;
import pl.com.qapps.unity.UnityPb.ResponsePb;
import pl.com.qapps.unity.UnityPb.ResponsePb.Status;

/**
 * Allows you to show notifications to user using NotifyOSD system.
 * 
 * @author Jakub Dykowski
 * 
 */
public final class NotifyOsd {

	private NotifyOsd() {
	}

	/**
	 * @param args
	 * @throws InterruptedException
	 */
	public static void main(String[] args) throws InterruptedException {
		
		Notification test = new Notification("Icon test", "", StockIcon.MESSAGE_IM);test.show();
		TimeUnit.SECONDS.sleep(3);
		int count = 0;
		for(StockIcon icon : StockIcon.values()) {
			count++;
			test.update("Icon test", icon.getName(), icon);
			if(count > 50)TimeUnit.SECONDS.sleep(1);
			else TimeUnit.MILLISECONDS.sleep(50);
		}
		
		Notification n = new Notification("Cole Raby", "jakis tekst",
				StockIcon.MESSAGE_EMAIL);
		n.show();
		TimeUnit.SECONDS.sleep(3);
		n.append("kolejna wiadomość w powiadomieniu");

		TimeUnit.SECONDS.sleep(3);
		long start = System.currentTimeMillis();
		n.update("Wiadomość od Peśka:", "kocham Cię Kubuś",
				"kadu");
		long stop = System.currentTimeMillis();
		System.out.println("delay: " + (stop - start));
		TimeUnit.SECONDS.sleep(3);
		n.append("tęsknie za Tobą kochanie");

		NotifyOsd.notify("kocham Cię Pesiu", null, StockIcon.AUDIO_PLAY);
	}

	/**
	 * This method is just simple wrapper around create new
	 * {@linkplain Notification} and {@linkplain #show(Notification)}
	 * 
	 * @param title
	 *            the title, can be <code>null</code>
	 * @param message
	 *            notification content, can be <code>null</code>
	 * @param icon
	 *            predefined icon, can be null
	 * @return newly created and shown notification
	 */
	public static Notification notify(String title, String message,
			StockIcon icon) {
		return notify(title, message, icon != null ? icon.getName() : null);
	}

	/**
	 * This method is just simple wrapper around create new
	 * {@linkplain Notification} and {@linkplain #show(Notification)}
	 * 
	 * @param title
	 *            the title, can be <code>null</code>
	 * @param message
	 *            notification content, can be <code>null</code>
	 * @param icon
	 *            the icon path, can be null
	 * @return newly created and shown notification
	 */
	public static Notification notify(String title, String message, String icon)
			throws NotificationException {
		Notification n = new Notification(title, message, icon);
		NotifyOsd.show(n);
		return n;
	}

	/**
	 * Updates existing notification, changing its content.
	 * 
	 * @param n
	 *            notification to be updated, not <code>null</code>
	 * @param updated
	 *            notification to be applied, not <code>null</code>
	 * @return the first, changed notification
	 * @throws NotificationException
	 *             if any internal error occurred
	 */
	public static Notification update(Notification n, Notification updated)
			throws NotificationException {
		synchronized (n) {
			try {
				ResponsePb res = Unity.connect().send(
						RequestPb
								.newBuilder()
								.setType(Type.NOTIFY_UPDATE)
								.setNotification(
										NotificationPb.newBuilder()
												.setIcon(updated.icon)
												.setMessage(updated.message)
												.setTitle(updated.title)
												.setId(n.id).build()).build());
				if (res.getResult() == Status.FAILURE)
					throw new RuntimeException("implementation failure: "
							+ res.getErrorMessage());
			} catch (IllegalStateException | IOException e) {
				throw new NotificationException(e);
			}
		}
		n.title = updated.title;
		n.message = updated.message;
		n.icon = updated.icon;
		return n;
	}

	/**
	 * Displays on desktop given notification if not shown already. The same
	 * {@linkplain Notification} objects can be used multiple times.
	 * 
	 * @param n
	 *            notification t be shown
	 * @return the given notification
	 * @throws NotificationException
	 *             if any internal error occurred
	 */
	public static Notification show(Notification n)
			throws NotificationException {
		try {
			ResponsePb res = Unity.connect().send(
					RequestPb
							.newBuilder()
							.setType(Type.NOTIFY)
							.setNotification(
									NotificationPb.newBuilder().setIcon(n.icon)
											.setMessage(n.message)
											.setTitle(n.title).setId(n.id)
											.build()).build());
			if (res.getResult() == Status.FAILURE)
				throw new RuntimeException("implementation failure: "
						+ res.getErrorMessage());
		} catch (IllegalStateException | IOException e) {
			throw new NotificationException(e);
		}
		return n;
	}

	/**
	 * Represents all necessary data required to show notification. Can be
	 * stored and used multiple times.
	 * 
	 * @author Jakub Dykowski
	 * 
	 */
	public static final class Notification {

		private static AtomicLong count = new AtomicLong(1);
		private final long id = count.getAndIncrement();
		private String title;
		private String message;
		private String icon;

		/**
		 * Creates new {@linkplain Notification} using predefined icon.
		 * 
		 * @param title
		 *            can be <code>null</code>
		 * @param message
		 *            can be <code>null</code>
		 * @param icon
		 *            can be <code>null</code>
		 */
		public Notification(String title, String message, StockIcon icon) {
			this(title, message, icon.getName());
		}

		/**
		 * Creates new {@linkplain Notification} using given icon path. If icon
		 * is not found no exception is thrown, as a result there will be jus
		 * blank space instead of icon.
		 * 
		 * @param title
		 * @param message
		 * @param icon
		 */
		public Notification(String title, String message, String icon) {
			super();
			this.title = title;
			this.message = message;
			this.icon = icon;
		}

		/**
		 * Updates this notification and if shown the content will be instantly
		 * changed.
		 * 
		 * @param title
		 *            can be <code>null</code>
		 * @param message
		 *            can be <code>null</code>
		 * @param icon
		 *            predefined icon, can be <code>null</code>
		 * @return the {@linkplain Notification} itself
		 */
		public Notification update(String title, String message, StockIcon icon) {
			return update(title, message, icon.getName());
		}

		/**
		 * Updates this notification and if shown the content will be instantly
		 * changed. If icon is not found no exception is throws, as a result
		 * there will be just blank space instead of icon.
		 * 
		 * @param title
		 *            can be <code>null</code>
		 * @param message
		 *            can be <code>null</code>
		 * @param icon
		 *            the icon path, can be <code>null</code>
		 * @return the {@linkplain Notification} itself
		 */
		public Notification update(String title, String message, String icon)
				throws NotificationException {
			return NotifyOsd.update(this,
					new Notification(title, message, icon));
		}

		/**
		 * If this notification is show or any other with the same title is, the
		 * text will be added to content of shown notification. If there are no
		 * notification with the same title, new one will be show.
		 * 
		 * @param message
		 *            the text to be added
		 * @return always new notification object, with new content
		 * @throws NotificationException
		 *             if any internal error occurred
		 */
		public Notification append(String message) throws NotificationException {

			// more convenient way than the one below it
			return NotifyOsd.notify(this.title, message, this.icon);

			// Notification updated = new Notification(this.title, message,
			// this.icon);
			// NotifyOsd.show(updated);
			// return updated;
		}

		/**
		 * Causes displaying this notification.
		 * 
		 * @return the {@linkplain Notification} itself
		 * @throws NotificationException
		 *             if any internal error occured
		 */
		public Notification show() throws NotificationException {
			return NotifyOsd.show(this);
		}
		
		@Override
		public String toString() {
			return "<Notification " + title + "|" + id + ">";
		}
	}

	/**
	 * Bunch of predefined system icons, like audio level, wireless network
	 * state or warning icon.
	 * 
	 * @author Jakub Dykowski
	 * 
	 */
	public enum StockIcon {

		AUDIO_NEXT("notification-audio-next"),
		AUDIO_PLAY("notification-audio-play"),
		AUDIO_PREVIOUS("notification-audio-previous"),
		AUDIO_VOLUME_HIGH("notification-audio-volume-high"),
		AUDIO_VOLUME_LOW("notification-audio-volume-low"),
		AUDIO_VOLUME_MEDIUM("notification-audio-volume-medium"),
		AUDIO_VOLUME_MUTED("notification-audio-volume-muted"),
		AUDIO_VOLUME_OFF("notification-audio-volume-off"),
		BATTERY_LOW("notification-battery-low"),
		DEVICE_EJECT("notification-device-eject"),
		DEVICE_FIREWIRE("notification-device-firewire"), 
		DIAPLAY_BRIGHTNESS_FULL("notification-display-brightness-full"),
		DISPLAY_BRIGHTNESS_HIGH("notification-display-brightness-high"),
		DISPLAY_BRIGHTNESS_LOW("notification-display-brightness-low"),
		DISPLAY_BRIGHTNESS_MEDIUM("notification-display-brightness-medium"), 
		DISPLAY_BRIGHTNESS_OFF("notification-display-brightness-off"), 
		GSM_3G_FULL("notification-gsm-3g-full"), 
		GSM_3G_HIGH("notification-gsm-3g-high"), 
		GSM_3G_LOW("notification-gsm-3g-low"), 
		GSM_3G_MEDIUM("notification-gsm-3g-medium"), 
		GSM_3G_NONE("notification-gsm-3g-none"),
		GSM_DISCONNECTED("notification-gsm-disconnected"), 
		GSM_EDGE_FULL("notification-gsm-edge-full"), 
		GSM_EDGE_HIGH("notification-gsm-edge-high"), 
		GSM_EDGE_LOW("notification-gsm-edge-low"), 
		GSM_EDGE_MEDIUM("notification-gsm-edge-medium"), 
		GSM_EDGE_NONE("notification-gsm-edge-none"), 
		GSM_FULL("notification-gsm-full"), 
		GSM_H_FULL("notification-gsm-h-full"), 
		GSM_H_HIGH("notification-gsm-h-high"), 
		GSM_HIGH("notification-gsm-high"), 
		GSM_H_MEDIUM("notification-gsm-h-medium"), 
		GSM_H_LOW("notification-gsm-h-low"),
		GSM_H_NONE("notification-gsm-h-none"), 
		GSM_LOW("notification-gsm-low"), 
		GSM_MEDIUM("notification-gsm-medium"), 
		GSM_NONE("notification-gsm-none"),
		KEYBOARD_BRIGHTNESS_FULL("notification-keyboard-brightness-full"), 
		KEYBOARD_BRIGHTNESS_HIGH("notification-keyboard-brightness-high"), 
		KEYBOARD_BRIGHTNESS_LOW("notification-keyboard-brightness-low"), 
		KEYBOARD_BRIGHTNESS_MEDIUM("notification-keyboard-brightness-medium"), 
		KEYBOARD_BRIGHTNESS_OFF("notification-keyboard-brightness-off"), 
		MESSAGE_EMAIL("notification-message-email"), 
		MESSAGE_IM("notification-message-im"), 
		NETWORK_ETHERNET_CONNECTED("notification-network-ethernet-connected"),
		NETWORK_ETHERNET_DISCONNECTED("notification-network-ethernet-disconnected"), 
		NETWORK_WIRELESS_CONNECTED("notification-network-wireless-disconnected"), 
		NETWORK_WIRELESS_FULL("notification-network-wireless-full"), 
		NETWORK_WIRELESS_HIGH("notification-network-wireless-high"), 
		NETWORK_WIRELESS_LOW("notification-network-wireless-low"), 
		NETWORK_WIRELESS_MEDIUM("notification-network-wireless-medium"), 
		NETWORK_WIRELESS_NONE("notification-network-wireless-none"),
		POWER_DISCONNECTED("notification-power-disconnected"),
		DIALOG_WARNING("dialog-warning"),
		DIALOG_QUESTION("dialog-question"),
		DiALOG_INFORMATION("dialog-information"),
		DIALOG_ERROR("error-dialog");

		private final String name;

		private StockIcon(String name) {
			this.name = name;
		}

		private String getName() {
			return name;
		}
	}

}
